using System;
using System.Collections.Generic;
using System.Text;
using DslFactory.Utilities.DteCodeModel;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.Modeling;
using System.Net.Security;

namespace Dslfactory.wcfLanguage
{
 internal static class InterfaceIncrementalCodeGeneration
 {
  /// <summary>
  /// Subscribe to Interface code generation events
  /// </summary>
  /// <param name="Store"></param>
  /// <param name="eventManagerDirectory"></param>
  public static void SubsribeToStoreEvents(Store Store)
  {
   EventManagerDirectory eventManagerDirectory = Store.EventManagerDirectory;

   // When a property of the Interface changes, the [ContractAttribute] must be updated in code
   eventManagerDirectory.ElementPropertyChanged.Add(Store.DomainDataDirectory.GetDomainClass(typeof(Interface)),
                                                          new EventHandler<ElementPropertyChangedEventArgs>(EnsureContractAttribute));

   // Special case for the CallbackContract (added, removed, changed) in code
   eventManagerDirectory.ElementAdded.Add(Store.DomainDataDirectory.GetDomainClass(typeof(ServiceContractReferencesCallbackContract)),
                                                          new EventHandler<ElementAddedEventArgs>(EnsureCallbackContract));
   eventManagerDirectory.ElementDeleted.Add(Store.DomainDataDirectory.GetDomainClass(typeof(ServiceContractReferencesCallbackContract)),
                                                          new EventHandler<ElementDeletedEventArgs>(EnsureNoCallbackContract));
   eventManagerDirectory.RolePlayerChanged.Add(Store.DomainDataDirectory.GetDomainClass(typeof(ServiceContractReferencesCallbackContract)),
                                                          new EventHandler<RolePlayerChangedEventArgs>(EnsureCallbackChanged));

   // When a property of the Method / OperationContract changes, the [OperationContract] attribute must be updated in code
   eventManagerDirectory.ElementPropertyChanged.Add(Store.DomainDataDirectory.GetDomainClass(typeof(Method)),
                                                               new EventHandler<ElementPropertyChangedEventArgs>(EnsureOperationContractAttribute));

   // When a method is removed from an Interface, the corresponding code must be deleted. When one is added, code must be added
   eventManagerDirectory.ElementDeleted.Add(Store.DomainDataDirectory.GetDomainClass(typeof(InterfaceHasMethods)),
                                                         new EventHandler<ElementDeletedEventArgs>(EnsuresMethodRemoved));
   eventManagerDirectory.ElementAdded.Add(Store.DomainDataDirectory.GetDomainClass(typeof(InterfaceHasMethods)),
                                                         new EventHandler<ElementAddedEventArgs>(EnsuresMethodAdded));
  }


  /// <summary>
  /// Ensures the callback contract interface of a contract interface is added 
  /// </summary>
  private static void EnsureCallbackContract(object sender, ElementAddedEventArgs arg)
  {
   SynchronizeCodeWithModel((arg.ModelElement as ServiceContractReferencesCallbackContract).SourceServiceContract);
  }

  /// <summary>
  /// Ensures the callback contract interface of a contract interface is removed 
  /// </summary>
  private static void EnsureNoCallbackContract(object sender, ElementEventArgs arg)
  {
   SynchronizeCodeWithModel((arg.ModelElement as ServiceContractReferencesCallbackContract).SourceServiceContract);
  }

  /// <summary>
  /// Ensures the callback contract interface of a contract interface changes 
  /// </summary>
  private static void EnsureCallbackChanged(object sender, RolePlayerChangedEventArgs arg)
  {
   SynchronizeCodeWithModel((arg.ElementLink as ServiceContractReferencesCallbackContract).SourceServiceContract);
  }


  /// <summary>
  /// Ensures the operation properties of which changed is updated
  /// </summary>
  private static void EnsureOperationContractAttribute(object sender, ElementPropertyChangedEventArgs arg)
  {
   // Method
   Method method = arg.ModelElement as Method;
   Interface iface = method.Interface;
   if (iface == null)
    return;

   KnownCodeTypes knownCodeTypes = KnownCodeTypes.FromStore(method.Store);
   if (knownCodeTypes == null)
    return;

   // Get the interface
   CodeInterface codeInterface = knownCodeTypes.GetFullNamedType(iface.FullName) as CodeInterface;
   if (codeInterface == null)
    return;

   if (codeInterface.InfoLocation != vsCMInfoLocation.vsCMInfoLocationProject)
    return;

   // Rename
   if (arg.DomainProperty.Id == Method.NameDomainPropertyId)
   {
    if (!string.IsNullOrEmpty(arg.NewValue as string))
    {
     // Rename the method
     string oldPrototype = method.Signature.Replace(arg.NewValue as string, arg.OldValue as string);
     CodeFunction codeFunction = IncrementalGenerator.FindMethod(codeInterface, oldPrototype);
     if (codeFunction != null)
      (codeFunction as CodeElement2).RenameSymbol(method.Name);
    }
   }

   // Signature
   else if (arg.DomainProperty.Id == Method.SignatureDomainPropertyId)
   {
    CodeFunction codeFunction = IncrementalGenerator.FindMethod(codeInterface, arg.OldValue as string);
    if (codeFunction != null)
     IncrementalGenerator.UpdateMethodPrototype(knownCodeTypes, codeFunction, method.Prototype);
   }

   // In any other case, update the attributes
   else
   {
    SynchronizeCodeWithModel(method);
   }
  }


  /// <summary>
  /// Ensure an attribute for interfaces
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="arg"></param>
  private static void EnsureContractAttribute(object sender, ElementPropertyChangedEventArgs arg)
  {
   Interface iface = arg.ModelElement as Interface;

   // Name
   if (arg.DomainProperty.Id == Interface.NameDomainPropertyId)
   {
    // Get the interface
    string oldFullName = (string.IsNullOrEmpty(iface.Namespace) ? arg.OldValue as string : iface.Namespace + '.' + arg.OldValue as string);
    KnownCodeTypes knownCodeTypes = KnownCodeTypes.FromStore(iface.Store);
    CodeInterface codeInterface = knownCodeTypes.GetFullNamedType(oldFullName) as CodeInterface;
    CodeElement2 element = codeInterface as CodeElement2;
    if (element != null)
    {
     element.RenameSymbol(arg.NewValue as string);
     knownCodeTypes.TakeIntoAccount(element as CodeType, true);
    }
   }

   else
    SynchronizeCodeWithModel(iface);
  }



  /// <summary>
  /// Synchronizes code from model for an Interface or a ServiceContract
  /// </summary>
  /// <param name="iface">Interface for which we want to update code</param>
  private static void SynchronizeCodeWithModel(Interface iface)
  {
   KnownCodeTypes knownCodeTypes = KnownCodeTypes.FromStore(iface.Store);
   if (knownCodeTypes == null)
    return;

   // Get the interface
   CodeInterface codeInterface = knownCodeTypes.GetFullNamedType(iface.FullName) as CodeInterface;
   if (codeInterface != null)
   {
    // Get the attribute (if necessary)
    CodeAttribute attribute = IncrementalGenerator.EnsureAttributeIff(codeInterface, "System.ServiceModel.ServiceContractAttribute", iface.IsServiceContract);

    // Properties are mapped to AttributeArguments
    ServiceContract serviceContract = iface as ServiceContract;
    if (serviceContract != null)
    {
     IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "Name", '"' + serviceContract.ServiceContractName + '"', !string.IsNullOrEmpty(serviceContract.ServiceContractName));
     IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "Namespace", '"' + serviceContract.ServiceContractNamespace + '"', !string.IsNullOrEmpty(serviceContract.ServiceContractNamespace));
     IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "ConfigurationName", '"' + serviceContract.ConfigurationName + '"', !string.IsNullOrEmpty(serviceContract.ConfigurationName));
     if (serviceContract.ProtectionLevel != ProtectionLevel.None)
      IncrementalGenerator.EnsureUsings(codeInterface.ProjectItem.FileCodeModel as FileCodeModel2, "System.Net.Security");
     IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "ProtectionLevel", "ProtectionLevel." + serviceContract.ProtectionLevel.ToString(), serviceContract.ProtectionLevel != ProtectionLevel.None);
     IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "SessionMode", "SessionMode." + serviceContract.SessionMode.ToString(), serviceContract.SessionMode != System.ServiceModel.SessionMode.Allowed);
     IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "CallbackContract",
                                                     serviceContract.CallbackContract != null ? "typeof(" + serviceContract.CallbackContract.FullName + ")" : null,
                                                     serviceContract.CallbackContract != null);
    }
   }

   // Generate methods
   foreach (Method method in iface.Methods)
   {
    IncrementalGenerator.EnsureMethod(codeInterface, method.Signature);
    SynchronizeCodeWithModel(method);
   }
  }


  /// <summary>
  /// Updates the method code from the model
  /// </summary>
  /// <param name="method"></param>
  private static void SynchronizeCodeWithModel(Method method)
  {
   if (method.Interface == null)
    return;

   KnownCodeTypes knownCodeTypes = KnownCodeTypes.FromStore(method.Store);
   if (knownCodeTypes == null)
    return;

   // Get the interface
   CodeInterface codeInterface = knownCodeTypes.GetFullNamedType(method.Interface.FullName) as CodeInterface;
   CodeFunction2 codeMethod = IncrementalGenerator.FindFirstMethod(codeInterface, method.Name);

   // Get the attribute (if necessary)
   CodeAttribute attribute = IncrementalGenerator.EnsureAttributeIff(codeMethod, "System.ServiceModel.OperationContractAttribute", method.IsOperationContract);

   // Properties
   OperationContract operationContract = method as OperationContract;
   if ( (method != null) && (attribute!=null) )
   {
    IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "Action", '"' + operationContract.Action + '"', !string.IsNullOrEmpty(operationContract.Action));
    IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "AsyncPattern", operationContract.AsyncPattern.ToString().ToLower(), operationContract.AsyncPattern);
    IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "IsOneWay", operationContract.IsOneWay.ToString().ToLower(), operationContract.IsOneWay);
    IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "IsInitiating", operationContract.IsInitiating.ToString().ToLower(), !operationContract.IsInitiating);
    IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "IsTerminating", operationContract.IsTerminating.ToString().ToLower(), operationContract.IsTerminating);
    IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "Name", '"' + operationContract.OperationContractName + '"', !string.IsNullOrEmpty(operationContract.OperationContractName));
    if (operationContract.ProtectionLevel != ProtectionLevel.None)
     IncrementalGenerator.EnsureUsings(codeInterface.ProjectItem.FileCodeModel as FileCodeModel2, "System.Net.Security");
    IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "ProtectionLevel", "ProtectionLevel." + operationContract.ProtectionLevel.ToString(), operationContract.ProtectionLevel != ProtectionLevel.None);
    IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "ReplyAction", '"' + operationContract.ReplyAction + '"', !string.IsNullOrEmpty(operationContract.ReplyAction));
   }
  }

  /// <summary>
  /// Ensures the operation properties of which changed is updated
  /// </summary>
  private static void EnsuresMethodRemoved(object sender, ElementEventArgs arg)
  {
   InterfaceHasMethods interfaceHasMethod = arg.ModelElement as InterfaceHasMethods;
   Method method = interfaceHasMethod.Method;
   Interface iface = interfaceHasMethod.Interface;

   // Important : Do not remove methods when the interface is removed from diagram
   if ( (iface.IsDeleting) || (iface.IsDeleted) )
    return;

   CodeInterface codeInterface = KnownCodeTypes.FromStore(method.Store).GetFullNamedType(iface.FullName) as CodeInterface;
   if (codeInterface == null)
    return;
   CodeFunction2 codeMethod = IncrementalGenerator.FindMethod(codeInterface, method.Signature);
   if (codeMethod == null)
    return;

   IncrementalGenerator.RemoveOrCommentMethod(codeMethod);
  }


  /// <summary>
  /// Ensures the operation properties of which changed is updated
  /// </summary>
  private static void EnsuresMethodAdded(object sender, ElementEventArgs arg)
  {
   InterfaceHasMethods interfaceHasMethod = arg.ModelElement as InterfaceHasMethods;
   Method method = interfaceHasMethod.Method;
   Interface iface = interfaceHasMethod.Interface;

   CodeInterface codeInterface = KnownCodeTypes.FromStore(method.Store).GetFullNamedType(iface.FullName) as CodeInterface;
   if (codeInterface == null)
    return;

   CodeFunction2 codeMethod = IncrementalGenerator.EnsureMethod(codeInterface, method.Signature);
   SynchronizeCodeWithModel(method);
  }


  internal static void Generate(TypeOfInterest type, CodeNamespace ns)
  {
   CodeInterface codeInterface = IncrementalGenerator.EnsureInterface(ns, type.Name);
   KnownCodeTypes.FromStore(type.Store).TakeIntoAccount(codeInterface as CodeType, false);
   SynchronizeCodeWithModel(type as Interface);
  }
 }
}
